package pl;

public abstract class BERTag {
	
	 public static final byte BER_TAG_CLASS_MASK_APPLICATION =	1;
	 public static final byte BER_TAG_CLASS_MASK_CONTEXT_SPECIFIC =	2;
	 public static final byte BER_TAG_CLASS_MASK_PRIVATE = 3;
	 public static final byte BER_TAG_CLASS_MASK_UNIVERSAL = 0;
	 public static final boolean BER_TAG_TYPE_CONSTRUCTED =	true;
	 public static final boolean BER_TAG_TYPE_PRIMITIVE = false;

	 boolean emptyTag;	
	 byte tagClass;
	 short tagNumber;
	 boolean tagPC;
	 short tagSizeInBytes;
               	   
	 protected 	BERTag() { }
	 
	 public abstract void init(byte[] bArray, short bOff);

	 public boolean isConstructed()
	 {
		 return tagPC;
	 }
 
	 public short toBytes(byte[] outBuf, short bOffset) throws ArrayIndexOutOfBoundsException
	 {
		 if (outBuf == null) throw new NullPointerException();
			
		 if ((short)(outBuf.length + bOffset) >=  (short)(tagSizeInBytes))
		 {
			 short offset = bOffset;
			 if (tagNumber < 31 )
			 {
				 outBuf[offset] = (byte) ((byte) (tagClass << 6) | tagNumber);
			 }
			 else if(tagNumber > 30 && tagNumber < 128)
			 {
				 outBuf[offset] = (byte) ((byte) (tagClass << 6) | 0x1F);
				 outBuf[(short)(offset + 1)] = (byte) tagNumber;
			 }
			 else if (tagNumber > 127 && tagNumber < 1024)
			 {
				 outBuf[offset] = (byte) ((byte) (tagClass << 6) | 0x1F);
				 outBuf[(short)(offset + 1)] = (byte) ((byte) 0x80 | (tagNumber / 4));
				 outBuf[(short)(offset + 2)] = (byte) (tagNumber % 128);
			 }
			 
			 if (tagPC)  outBuf[offset] |= 0x20;
		 }
		 else
		 {
			 throw new ArrayIndexOutOfBoundsException(); 
		 }
		 
		 return tagSizeInBytes;
	 }
	 
	 public byte tagClass()
	 {
		 return tagClass;
	 }
	 
	 public short tagNumber()
	 {
		 return tagNumber;
	 }
	 
	 public byte size()
	 {
		 return (byte) this.tagSizeInBytes;
	 }
	 // Tested
	 public static BERTag getInstance(byte[] bArray, short bOff)
	 {
		 BERTag berTmp = null; 
		 
		 if (bArray == null) throw new NullPointerException();
		 
		 if ((short)(bArray.length) > bOff)
		 {
			 // primitive or constructed
			 if ((bArray[bOff] & 0x20) == 0x20)
			 {
				 berTmp = new ConstructedBERTag();
				 berTmp.tagPC = BER_TAG_TYPE_CONSTRUCTED;	 
			 }
			 else
			 {
				 berTmp = new PrimitiveBERTag();
				 berTmp.tagPC = BER_TAG_TYPE_PRIMITIVE;	
			 }
			 
			 berTmp.tagClass =  (byte) (((byte) (bArray[bOff] & 0xC0)>> 6) & 0x03);
			 
			 if ((bArray[bOff] & 0x1F) == 0x1F)
			 {
				 // TAG has 2 bytes
				 berTmp.tagNumber = (byte) (bArray[(short)(bOff + 1)]);
				 berTmp.tagSizeInBytes = 0x02;
			 }
			 else
			 {
				 berTmp.tagNumber = (byte) (bArray[bOff] & 0x1F);
				 berTmp.tagSizeInBytes = 0x01;
			 }
			 
			 berTmp.emptyTag = false;	 
		 }
		 else
		 {
			 throw new ArrayIndexOutOfBoundsException(); 
		 }
		 
		 return berTmp;
	 }
	 
	 
	 public boolean equals(BERTag otherTag)
	 {
		 if (this.tagPC != otherTag.tagPC) return false;
		 if (this.emptyTag != otherTag.emptyTag) return false;
		 if (this.tagClass != otherTag.tagClass) return false;
		 if (this.tagNumber != otherTag.tagNumber) return false;
		 if (this.tagSizeInBytes != otherTag.tagSizeInBytes) return false;
		 return true;
	 }
	 
}
